/* IconMosaicView.m -- copyright 1993 by Christopher Lane */

#import <c.h>
#import <sys/dir.h>
#import <mach-o/loader.h>
#import <appkit/appkit.h>

#import "Thinker.h"
#import "DefaultsTable.h"
#import "IconMosaicView.h"

#define MODULE "IconMosaic"
#define DEFAULTSFILE "Defaults.strings"

#define IMAGEPATHS getDefault("IconMosaicImagePaths")
#define SEGMENTLIST getDefault("IconMosaicSegmentList")
#define IMAGESUFFIXLIST getDefault("IconMosaicImageSuffixList")
#define APPSUFFIXLIST getDefault("IconMosaicApplicationSuffixList")
#define IGNORESECTIONLIST getDefault("IconMosaicIgnoreSectionList")

#define MAXIMUMSIZE getSizeDefault("IconMosaicMaximumSize")
#define MAXIMUMBYTES getIntDefault("IconMosaicMaximumBytes")

#define DENSITYDEFAULTNAME "IconMosaicDensity"
#define DENSITY getFloatDefault(DENSITYDEFAULTNAME)

#define PATHSEPARATOR "/"
#define PATHSEPARATORCHAR (PATHSEPARATOR)[0]
#define TOKENSEPARATOR ":"
#define TOKENSEPARATORCHAR (TOKENSEPARATOR)[0]
#define EXTENSIONSEPARATOR "."
#define EXTENSIONSEPARATORCHAR (EXTENSIONSEPARATOR)[0]

@implementation IconMosaicView

static any_t search(id self)
{
	char *path, *paths = NXCopyStringBuffer(IMAGEPATHS);
	
	for(path = strtok(paths, TOKENSEPARATOR); path != NULL; path = strtok(NULL, TOKENSEPARATOR))
		[self loadImagesFromDirectory:path];
		
	NX_FREE(paths);
	
	return self;
}

- initFrame:(NXRect *) frameRect
{
	char buffer[MAXPATHLEN];
	
	[super initFrame:frameRect];

	[self allocateGState];

	images = [[List alloc] init];
	
	inspector = field = nil;
	
	thread = NO_CTHREAD;
	
	lock = mutex_alloc();
	condition = condition_alloc();
	
	(void) sprintf(buffer,"%s/%s", [[NXApp delegate] moduleDirectory:MODULE], DEFAULTSFILE);
	[(defaults = [[DefaultsTable alloc] initFromFile:buffer]) registerDefaults:[NXApp appName]];

	maximumSize = MAXIMUMSIZE;
	
	maximumBytes = MAXIMUMBYTES;
	
	srandom(time(NULL));
	
	return self;
}

- drawSelf:(const NXRect *) rects :(int) rectCount
{
	if (rects == NULL || rectCount == 0) return self;
	
	PSsetgray(NX_BLACK);

	NXRectFill(rects);

	return self;
}

- didLockFocus
{
	if(thread == NO_CTHREAD) cthread_detach(thread = cthread_fork((cthread_fn_t) search, (any_t) self));
		
	return self;
}

- free
{
	mutex_lock(lock); {
		[[images freeObjects] free];
		(void) cthread_abort(thread);
		} mutex_unlock(lock);
		
	return [super free];
}

- oneStep
{
	id image;
	NXSize size;
	unsigned int count;
	NXPoint point = { 0, 1 };
	
	condition_signal(condition);
	
	[self scrollRect:&bounds by:&point];
	
	if(((random() % 100) / 100.0) > [density floatValue]) return self;
	
	mutex_lock(lock); {
		count = [images count];
		} mutex_unlock(lock);
	
	if(count < 1) return self;
	
	mutex_lock(lock); {
		[(image = [images objectAt:(random() % count)]) getSize:&size];
		} mutex_unlock(lock);

	point.x = random() % (int) (bounds.size.width - size.width);

	[image composite:NX_SOVER toPoint:&point];
	
	if(field != nil && [field intValue] < count) [field setIntValue:count];

	return self;
}

- inspector:sender
{
	char buffer[MAXPATHLEN];

	if (inspector == nil) {
		(void) sprintf(buffer,"%s/%s.nib", [sender moduleDirectory:MODULE], MODULE);
		[NXApp loadNibFile:buffer owner:self withNames:NO];
		}

	return inspector;
}

- inspectorInstalled
{ 
	mutex_lock(lock); {
		[field setIntValue:[images count]];
		} mutex_unlock(lock);

	 return self;
}

- (BOOL) useBufferedWindow { return YES; }

- (const char *) windowTitle { return MODULE; }

- setDefault:sender
{
	[sender setTag:YES];
	
	if([density tag]) (void) writeDefault(DENSITYDEFAULTNAME, [density stringValue]);

	return self;
}

- setDensity:anObject
{
	[(density = anObject) setFloatValue:DENSITY];
	
	return self;
}

- (BOOL) loadFromStream:(NXStream *) stream
{
	NXSize size;
	BOOL result = NO;
	NXImage *image = [[NXImage alloc] init];

	if([image loadFromStream:stream]) {
		[image getSize:&size];
		if(size.width <= maximumSize.width && size.height <= maximumSize.height) {
			mutex_lock(lock); {
				[images addObject:image];
				condition_wait(condition, lock);
				} mutex_unlock(lock);
			result = YES;
			}
		else [image free];
		}
	else [image free];
	
	return result;
}

- (BOOL) member:(const char *) item ofSet:(const char *) set
{
	BOOL result = NO;
	char *r, *s, *buffer = NXCopyStringBuffer(set);
	int index, length, itemLength = strlen(item);
	
	r = buffer;
	
	while((s = r) != NULL) {
		if((r = index(s, TOKENSEPARATORCHAR)) != NULL) *r++ = '\0';
		
		if((length = strlen(s)) > itemLength) continue;
		else index = itemLength - length;
		
		if(result = (strncmp(s, item + index, length) == 0)) break;
		}
		
	NX_FREE(buffer);
	
	return result;
}

- loadImagesFromDirectory:(const char *) directory
{
	struct stat fs;
	NXStream *stream;
	struct direct *dp;
	char buffer[MAXPATHLEN];
	DIR *dirp = opendir(directory);
	
	if(dirp == NULL) return nil;

	for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
		(void) strcat(strcat(strcpy(buffer, directory), PATHSEPARATOR), dp->d_name);
		if(stat(buffer, &fs) == CERROR) continue;
		else if(fs.st_mode & S_IFDIR && (dp->d_name)[0] != EXTENSIONSEPARATORCHAR) [self loadImagesFromDirectory:buffer];
		else if(fs.st_size <= maximumBytes && [self member:dp->d_name ofSet:IMAGESUFFIXLIST]) {
			if((stream = NXMapFile(buffer, NX_READONLY)) != NULL) {
				if([self loadFromStream:stream]) {
#ifdef DEBUG
					(void) fprintf(stderr, "%s\n", buffer);
					}
				else {
					(void) fprintf(stderr, "%s -- failed\n", buffer);
#endif
					}
				NXCloseMemory(stream, NX_FREEBUFFER);
				}
			}
		else if(fs.st_mode & S_IEXEC && fs.st_mode & S_IFREG) [self loadImagesFromSegment:buffer];
		}

	(void) closedir(dirp);
	
	return self;
}

- loadImagesFromSegment:(const char *) filename
{
	long offset;
	char *buffer;
	unsigned int i;
	NXStream *stream, *memory;
	unsigned long ncmds, nsects;

	struct mach_header m_header;
	struct load_command l_command;
	struct segment_command s_command;
	struct section s_section;

	if ((stream = NXMapFile(filename, NX_READONLY)) == NULL) return nil;
	if (NXRead(stream, &m_header, sizeof(m_header)) == sizeof(m_header) && m_header.magic == MH_MAGIC) {
	
		for (ncmds = m_header.ncmds; ncmds > 0; ncmds--) {
			offset = NXTell(stream);
			if (NXRead(stream, &l_command, sizeof(l_command)) != sizeof(l_command)) break;
			if (l_command.cmd == LC_SEGMENT) {
				NXSeek(stream, offset, NX_FROMSTART);
				if (NXRead(stream, &s_command, sizeof(s_command)) != sizeof(s_command)) break;
				if([self member:s_command.segname ofSet:SEGMENTLIST]) {
					for (nsects = 0; nsects < s_command.nsects; nsects++) {
						if (NXRead(stream, &s_section, sizeof(s_section)) != sizeof(s_section)) continue;
						else if(s_section.size > maximumBytes || [self member:s_section.sectname ofSet:IGNORESECTIONLIST]) continue;
					
						offset = NXTell(stream);
						NXSeek(stream, s_section.offset, NX_FROMSTART);

						(void) vm_allocate(task_self(), (vm_address_t *) &buffer, (vm_size_t) s_section.size, TRUE);
						for (i = 0; i < s_section.size; i++) buffer[i] = NXGetc(stream);
					
						if((memory = NXOpenMemory(buffer, s_section.size, NX_READONLY)) != NULL) {
							if([self loadFromStream:memory]) {
#ifdef DEBUG
								(void) fprintf(stderr, "%s:%s:%s\n", filename, s_section.segname, s_section.sectname);
								}
							else {
								(void) fprintf(stderr, "%s:%s:%s -- failed\n", filename, s_section.segname, s_section.sectname);
#endif
								}
					
							NXCloseMemory(memory, NX_FREEBUFFER);
							}
						NXSeek(stream, offset, NX_FROMSTART);
						}
					}
				else NXSeek(stream, s_command.cmdsize - sizeof(s_command), NX_FROMCURRENT);
				}
			else NXSeek(stream, l_command.cmdsize - sizeof(l_command), NX_FROMCURRENT);
			}
		}
		
	NXCloseMemory(stream, NX_FREEBUFFER);
	
	return self;
}

@end
